/*
 * barrier.c
 *
 *  Created on: 22/05/2012
 *      Author: Lior
 */

#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include "hw3_helpers.h"
#include "doubly_conc_ll.h"

#define MAX_LINE_SIZE 128

typedef enum {INSERT_HEAD=0,INSERT_TAIL,DELETE,SEARCH} THREAD_TYPE;
	
typedef struct insertArg {
	int key;
	char data;
	int thread_index;
} threadInfo;


int thread_counter,prev_thread_counter,wait_on_barrier;
pthread_t* threads_array;
pthread_mutex_t mutex_barrier,mutex_print;
pthread_cond_t cond_barrier;


void join_all_threads() {
	if (prev_thread_counter > 0) {
		ClearDeleted();
		for (int i=0;i<prev_thread_counter;i++) {
			void* tmp;
			pthread_join(threads_array[i],&tmp);
		}
		free(threads_array);
		threads_array = NULL;
	}
	prev_thread_counter=thread_counter;
	thread_counter=0;
}

void thread_start(threadInfo* t) {
	pthread_mutex_lock(&mutex_barrier);
		wait_on_barrier++;
		pthread_cond_wait(&cond_barrier,&mutex_barrier);
		threads_array[t->thread_index]=pthread_self();
		wait_on_barrier--;
	pthread_mutex_unlock(&mutex_barrier);
}

void Barrier() {
	printf("BARRIER\n");
	//before each barrier execute pthread_join on all the threads from the previous burst
	join_all_threads();
	//allocate a new array for all the threads in the current burst
	if (prev_thread_counter) {
		threads_array = malloc(prev_thread_counter*sizeof(pthread_t));
		if (threads_array==NULL) {
		assert(0);
		}
	}
	/*
	 * now, wait until all threads have waited on the CV, then wake up all threads.
	 * you have to lock the lock first because maybe some threads updated wait_on_barrier
	 * but didn't make it to lock the lock.
	 */
	while (wait_on_barrier != prev_thread_counter) {}
	pthread_mutex_lock(&mutex_barrier);
		pthread_cond_broadcast(&cond_barrier);
	pthread_mutex_unlock(&mutex_barrier);
	//once a thread wakes up it writes its thread_id to the above array and decreases wait_on_barrier by 1. Only when all threads did it you can continue running
	while (wait_on_barrier) {}
}

void* ParserInsertHead(void* arg) {
	bool res;
	threadInfo* t = ((threadInfo*) arg);
	thread_start(t);
		res = InsertHead(t->key,t->data);
		pthread_mutex_lock(&mutex_print);
		if (res) printf("INSERT_HEAD %d %c->TRUE\n",t->key,t->data);
		else printf("INSERT_HEAD %d %c->FALSE\n",t->key,t->data);
		pthread_mutex_unlock(&mutex_print);
	free(t);
	return 0;
}

void* ParserInsertTail(void* arg) {
	bool res;
	threadInfo* t = ((threadInfo*) arg);
	thread_start(t);
		res = InsertTail(t->key,t->data);
		pthread_mutex_lock(&mutex_print);
		if (res) printf("INSERT_TAIL %d %c->TRUE\n",t->key,t->data);
		else printf("INSERT_TAIL %d %c->FALSE\n",t->key,t->data);
		pthread_mutex_unlock(&mutex_print);
	free(t);
	return 0;
}

void* ParserDelete(void* arg) {
	bool res;
	threadInfo* t = ((threadInfo*) arg);
	thread_start(t);
		res = Delete(t->key);
		pthread_mutex_lock(&mutex_print);
		if (res) printf("DELETE %d->TRUE\n",t->key);
		else printf("DELETE %d->FALSE\n",t->key);
		pthread_mutex_unlock(&mutex_print);
	free(t);
	return 0;
}

void* ParserSearch(void* arg) {
	bool res;
	char node_data;
	threadInfo* t = ((threadInfo*) arg);
	thread_start(t);
		res = Search(t->key,&node_data);
		pthread_mutex_lock(&mutex_print);
		if(res) printf("SEARCH %d->%c\n",t->key,node_data);
		else printf("SEARCH %d->FALSE\n",t->key);
		pthread_mutex_unlock(&mutex_print);
	free(t);
	return 0;
}

void End() {
	Barrier();
	join_all_threads();
	//destroy everything
	pthread_mutex_destroy(&mutex_barrier);
	pthread_mutex_destroy(&mutex_print);
	pthread_cond_destroy(&cond_barrier);
	Destroy();		// destroying the list
	printf("END\n");
}

void Begin() {
	prev_thread_counter=0;
	thread_counter=0;
	wait_on_barrier=0;
	ec_mutex_init(&mutex_barrier);
	ec_mutex_init(&mutex_print);
	pthread_cond_init(&cond_barrier,NULL);
	Initialize();	// initializing the list
	printf("BEGIN\n");
}


void setNewThread(int commandType,int key,char data) {
	pthread_t newThread;
	threadInfo* arg = (threadInfo*) malloc(sizeof(threadInfo));
	arg->key = key;
	arg->data = data;
	arg->thread_index = thread_counter;
	int res;
	switch(commandType) {
		case INSERT_HEAD:
			res=pthread_create(&newThread,NULL,&ParserInsertHead,arg);
			break;
		case INSERT_TAIL:
			res=pthread_create(&newThread,NULL,&ParserInsertTail,arg);
			break;
		case DELETE:
			res=pthread_create(&newThread,NULL,&ParserDelete,arg);
			break;
		case SEARCH:
			res=pthread_create(&newThread,NULL,&ParserSearch,arg);
			break;
		default :
			free(arg);
			printf("\n *** BUG ***\n");
			break;
	}
	if (res == 0) {
		thread_counter++;
	}
}

void parseInput() {
	char cmd[MAX_LINE_SIZE],line[MAX_LINE_SIZE];
	while (!feof(stdin)) {
		if (fgets(line,MAX_LINE_SIZE,stdin) == NULL) break;
		else {
			int key;
			char data;
			sscanf(line,"%s %d %c",cmd,&key,&data);
			if (!strcmp(cmd,"BEGIN")) Begin();
			else if (!strcmp(cmd,"INSERT_HEAD")) setNewThread(INSERT_HEAD,key,data);
			else if (!strcmp(cmd,"INSERT_TAIL")) setNewThread(INSERT_TAIL,key,data);
			else if (!strcmp(cmd,"DELETE")) setNewThread(DELETE,key,'1');
			else if (!strcmp(cmd,"SEARCH")) setNewThread(SEARCH,key,'1');
			else if (!strcmp(cmd,"BARRIER")) Barrier();
			else if (!strcmp(cmd,"END")) End();
			else if (!strcmp(cmd,"EXIT")) break;
			else printf("Illegal command!\n");
		}
	}
}

int main() {
	parseInput();
	return 0;
}
